package com.bkhech.netty.httpwebsocket.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.CharsetUtil;

import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * HttpWebSocketServer
 * <p>
 * 基于 netty，根据请求路径，同时实现 http 服务和 websocket 服务
 * <p>
 * 当客户端发送一个 HTTP 请求到 /hello 路径时，服务器会将该HTTP连接升级为 WebSocket 协议。
 * <p>
 * http 请求示例：curl -i http://localhost:8080/api/info
 * <p>
 * websocket 请求示例：wscat -c ws://localhost:8080/hello
 *
 * @author guowm
 * @date 2024/3/30
 */
public class HttpWebSocketServer {

    private final int port;

    public HttpWebSocketServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            //因为基于http协议，使用http的编码和解码器
                            p.addLast(new HttpServerCodec());
                             /*
                                1.http数据在传输过程中是分段，HttpObjectAggregator就是可以将多个段聚合，这就解释了当浏览器发送大量数据时，会发出多次http请求的问题
                                2.这个聚合器主要是将 HttpMessage 聚合成http为一个完整的报文，即 FullHttpRequest/FullHttpResponse
                             */
                            p.addLast(new HttpObjectAggregator(65536));
                            //自定义的handler，处理http业务逻辑
                            p.addLast(new HttpHandler());
                        }
                    });

            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new HttpWebSocketServer(8080).run();
    }

    public static class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
            if (request.decoderResult().isFailure()) {
                sendError(ctx, HttpResponseStatus.BAD_REQUEST);
                return;
            }

            String uri = request.uri();
            if ("/hello".equalsIgnoreCase(uri)) {
                System.out.println("---------WebSocketHandler Handle WebSocket request");
                 // 转换为WebSocket协议
                 /*
                    1.对应websocket，它的数据是以帧（frame）形式传递
                    2.可以看到WebSocketFrame下面的六个子类
                    3.浏览器请求时，ws://localhost:8080/hello表示请求的url
                    4.WebSocketServerProtocolHandler核心功能是将http协议升级为ws协议，保持长连接
                    5.是通过一个状态码101
                    6. 此 Handler 会帮我们处理一些比较复杂的繁重的操作。
                        会处理一些握手操作：handShaking（close,ping,pong），ping + pong = 心跳
                        对于 WebSocket 来说，数据都是以 frames 进行传输的，不同的数据类型所对应的 frames 也都不同
                     */
                ctx.pipeline().addLast("webSocketProtocolHandler", new WebSocketServerProtocolHandler("/hello"));
                //自定义的handler，处理websocket业务逻辑
                ctx.pipeline().addLast("websocketHandler", new WebSocketHandler());


                // 用于将消息传递给下一个ChannelInboundHandler处理器的方法。
                // 这里这行代码的作用是将HTTP请求消息传递给下一个处理器 WebSocketServerProtocolHandler，
                // 以便开始处理 WebSocket 协议的握手和后续的消息
                ctx.fireChannelRead(request.retain());

                // 重点：放在最后面，不能放在前面先删除，会把当前正在处理的 Handler 删掉
                ctx.pipeline().remove(this);
            } else {
                System.out.println("---------HttpHandler Handle HTTP request");
                sendResponse(ctx, uri);
            }
        }

        private void sendResponse(ChannelHandlerContext ctx, String message) {
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.copiedBuffer("服务器时间" + LocalDateTime.now() + ": Hello, uri is " + message + "\r\n", CharsetUtil.UTF_8));
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }

        private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    status,
                    Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }

    public static class WebSocketHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

        @Override
        public void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
            // 处理 websocket 消息
            // 处理文本WebSocket消息
            if (frame instanceof TextWebSocketFrame) {
                String text = ((TextWebSocketFrame) frame).text();

                System.out.println("[TextWebSocketFrame]服务器收到消息: " + text);

                ctx.writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + ": " + text));
            } else if (frame instanceof BinaryWebSocketFrame) {
                // 处理二进制WebSocket消息
                BinaryWebSocketFrame binaryFrame = ((BinaryWebSocketFrame) frame);
                final ByteBuf binaryData = binaryFrame.content();

                // 这里可以处理二进制数据，比如打印等
                StringBuffer sb = new StringBuffer();
                Arrays.stream(new int[binaryData.readableBytes()]).forEach(item -> sb.append(binaryData.readByte()));
                System.out.println("[BinaryWebSocketFrame]服务器收到消息: " + sb);

                ctx.writeAndFlush(new TextWebSocketFrame("[BinaryWebSocketFrame]服务器收到消息: " + sb));
            } else if (frame instanceof PingWebSocketFrame) {
                ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            } else if (frame instanceof CloseWebSocketFrame) {
                ctx.close();
            }
            // 用于将消息传递给下一个ChannelInboundHandler处理器的方法。最后一个就不要传递了
//            ctx.fireChannelRead(frame);
        }

        private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {

        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }

    }
}
